using System;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * Objects that represent instructions or expressions
     * in the compiled tree representation of the template
     * all descend from this abstract base class.
     */

    public abstract class TemplateObject
    {
        private Template.Template _template;
        protected int BeginColumn, BeginLine, EndColumn, EndLine;

        internal void SetLocation(Template.Template template, Token begin, Token end)
        {
            SetLocation(template, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
        }

        internal void SetLocation(Template.Template template, Token begin, TemplateObject end)
        {
            SetLocation(template, begin.beginColumn, begin.beginLine, end.EndColumn, end.EndLine);
        }

        internal void SetLocation(Template.Template template, TemplateObject begin, Token end)
        {
            SetLocation(template, begin.BeginColumn, begin.BeginLine, end.endColumn, end.endLine);
        }

        internal void SetLocation(Template.Template template, TemplateObject begin, TemplateObject end)
        {
            SetLocation(template, begin.BeginColumn, begin.BeginLine, end.EndColumn, end.EndLine);
        }

        public int GetBeginColumn()
        {
            return BeginColumn;
        }

        public int GetBeginLine()
        {
            return BeginLine;
        }

        public int GetEndColumn()
        {
            return EndColumn;
        }

        public int GetEndLine()
        {
            return EndLine;
        }

        internal virtual void SetLocation(Template.Template template, int beginColumn, int beginLine, int endColumn, int endLine)
        {
            _template = template;
            BeginColumn = beginColumn;
            BeginLine = beginLine;
            EndColumn = endColumn;
            EndLine = endLine;
        }

        internal static void AssertNonNull(ITemplateModel model, Expression exp, Environment env)
        {
            if (model == null) {
                throw new InvalidReferenceException(
                    "Expression " + exp + " is undefined " +
                    exp.GetStartLocation() + ".", env);
            }
        }

        protected static TemplateException InvalidTypeException(
            ITemplateModel model, Expression exp, Environment env, string expected)
        {
            AssertNonNull(model, exp, env);
            return new TemplateException(
                "Expected " + expected + ". " +
                exp + " evaluated instead to " +
                model.GetType().FullName + " " +
                exp.GetStartLocation() + ".", env);
        }

        /**
         * Returns a string that indicates
         * where in the template source, this object is.
         */

        public string GetStartLocation()
        {
            string templateName = _template != null ? _template.GetName() : "input";
            return "on line "
                  + BeginLine
                  + ", column "
                  + BeginColumn
                  + " in "
                  + templateName;
        }

        public string GetEndLocation()
        {
            string templateName = _template != null ? _template.GetName() : "input";
            return "on line "
                  + EndLine
                  + ", column "
                  + EndColumn
                  + " in "
                  + templateName;
        }

        public string GetSource()
        {
            return _template != null
                ? _template.GetSource(BeginColumn, BeginLine, EndColumn, EndLine)
                : GetCanonicalForm();
        }

        public override string ToString()
        {
            try
            {
                return GetSource();
            }
            catch (Exception)
            {
                // REVISIT: A bit of a hack? (JR)
                return GetCanonicalForm();
            }
        }

        /**
         * @return whether the point in the template file specified by the 
         * column and line numbers is contained within this template object.
         */
        public bool Contains(int column, int line)
        {
            if (line < BeginLine || line > EndLine)
            {
                return false;
            }
            if (line == BeginLine)
            {
                if (column < BeginColumn)
                {
                    return false;
                }
            }
            if (line == EndLine)
            {
                if (column > EndColumn)
                {
                    return false;
                }
            }
            return true;
        }

        public Template.Template GetTemplate()
        {
            return _template;
        }

        protected internal TemplateObject CopyLocationFrom(TemplateObject from)
        {
            _template = from._template;
            BeginColumn = from.BeginColumn;
            BeginLine = from.BeginLine;
            EndColumn = from.EndColumn;
            EndLine = from.EndLine;
            return this;
        }    

        public abstract string GetCanonicalForm();
    }
}